Passed
Push — task/refactor-application-data... ( 30e714...e7ce5c )
by Tristan
04:51
created

applicationHooks.tsx ➔ useJobPosterQuestions   A

Complexity

Conditions 2

Size

Total Lines 6
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 5
dl 0
loc 6
rs 10
c 0
b 0
f 0
cc 2
1
/* eslint-disable camelcase */
2
import { useCallback, useEffect, useState } from "react";
3
import { useSelector } from "react-redux";
4
import { DispatchType } from "../configureStore";
5
import { RootState } from "../store/store";
6
import { getAwardRecipientTypes as fetchAwardRecipientTypes } from "../store/AwardRecipientType/awardRecipientTypeActions";
7
import { getAwardRecipientTypes } from "../store/AwardRecipientType/awardRecipientTypeSelector";
8
import { getAwardRecognitionTypes as fetchAwardRecognitionTypes } from "../store/AwardRecognitionType/awardRecognitionTypeActions";
9
import { getAwardRecognitionTypes } from "../store/AwardRecognitionType/awardRecognitionTypeSelector";
10
import { getEducationTypes as fetchEducationTypes } from "../store/EducationType/educationTypeActions";
11
import { getEducationTypes } from "../store/EducationType/educationTypeSelector";
12
import { getEducationStatuses as fetchEducationStatuses } from "../store/EducationStatus/educationStatusActions";
13
import { getEducationStatuses } from "../store/EducationStatus/educationStatusSelector";
14
import {
15
  AwardRecipientType,
16
  AwardRecognitionType,
17
  EducationType,
18
  EducationStatus,
19
  Job,
20
  Experience as ExperienceType,
21
  Skill,
22
  ExperienceSkill,
23
  ApplicationNormalized,
24
  Criteria,
25
  JobPosterQuestion,
26
  JobApplicationAnswer,
27
} from "../models/types";
28
import {
29
  getApplicationIsUpdating,
30
  getApplicationNormalized,
31
  getJobApplicationAnswers,
32
} from "../store/Application/applicationSelector";
33
import { fetchApplicationNormalized } from "../store/Application/applicationActions";
34
import {
35
  getCriteriaByJob,
36
  getJob,
37
  getJobIsUpdating,
38
  getJobPosterQuestionsByJob,
39
} from "../store/Job/jobSelector";
40
import { fetchJob } from "../store/Job/jobActions";
41
import { ApplicationStatusId } from "../models/lookupConstants";
42
import {
43
  getExperienceByApplicant,
44
  getExperienceByApplication,
45
  getExperienceSkillsByApplicant,
46
  getExperienceSkillsByApplication,
47
  getUpdatingByApplicant,
48
  getUpdatingByApplication,
49
} from "../store/Experience/experienceSelector";
50
import {
51
  fetchExperienceByApplicant,
52
  fetchExperienceByApplication,
53
} from "../store/Experience/experienceActions";
54
import { getSkills, getSkillsUpdating } from "../store/Skill/skillSelector";
55
import { fetchSkills } from "../store/Skill/skillActions";
56
57
export function useApplication(
58
  applicationId: number,
59
): ApplicationNormalized | null {
60
  return useSelector((state: RootState) =>
61
    getApplicationNormalized(state, { applicationId }),
62
  );
63
}
64
65
export function useJob(jobId: number | undefined): Job | null {
66
  return useSelector((state: RootState) =>
67
    jobId ? getJob(state, { jobId }) : null,
68
  );
69
}
70
71
export function useExperienceConstants(): {
72
  awardRecipientTypes: AwardRecipientType[];
73
  awardRecognitionTypes: AwardRecognitionType[];
74
  educationTypes: EducationType[];
75
  educationStatuses: EducationStatus[];
76
} {
77
  const awardRecipientTypes = useSelector(getAwardRecipientTypes);
78
  const awardRecognitionTypes = useSelector(getAwardRecognitionTypes);
79
  const educationTypes = useSelector(getEducationTypes);
80
  const educationStatuses = useSelector(getEducationStatuses);
81
  return {
82
    awardRecipientTypes,
83
    awardRecognitionTypes,
84
    educationTypes,
85
    educationStatuses,
86
  };
87
}
88
89
export function useSkills(): Skill[] {
90
  return useSelector(getSkills);
91
}
92
93
export function useCriteria(jobId: number | undefined): Criteria[] {
94
  return useSelector((state: RootState) =>
95
    jobId ? getCriteriaByJob(state, { jobId }) : [],
96
  );
97
}
98
99
export function useExperiences(
100
  applicationId: number,
101
  application: ApplicationNormalized | null,
102
): ExperienceType[] {
103
  const applicantId = application?.applicant_id ?? 0;
104
105
  // When an Application is still a draft, use Experiences associated with the applicant profile.
106
  // When an Application has been submitted and is no longer a draft, display Experience associated with the Application directly.
107
  const useProfileExperience =
108
    application === null ||
109
    application.application_status_id === ApplicationStatusId.draft;
110
111
  // This selector must be memoized because getExperienceByApplicant/Application uses reselect, and not re-reselect, so it needs to preserve its state.
112
  const experienceSelector = useCallback(
113
    (state: RootState) =>
114
      useProfileExperience
115
        ? getExperienceByApplicant(state, { applicantId })
116
        : getExperienceByApplication(state, { applicationId }),
117
    [applicationId, applicantId, useProfileExperience],
118
  );
119
  const experiencesByType = useSelector(experienceSelector);
120
  const experiences: ExperienceType[] = [
121
    ...experiencesByType.award,
122
    ...experiencesByType.community,
123
    ...experiencesByType.education,
124
    ...experiencesByType.personal,
125
    ...experiencesByType.work,
126
  ];
127
  return experiences;
128
}
129
130
export function useExperienceSkills(
131
  applicationId: number,
132
  application: ApplicationNormalized | null,
133
): ExperienceSkill[] {
134
  // ExperienceSkills don't need to be fetched because they are returned in the Experiences API calls.
135
  const applicantId = application?.applicant_id ?? 0;
136
  const useProfileExperience =
137
    application === null ||
138
    application.application_status_id === ApplicationStatusId.draft;
139
  const expSkillSelector = (state: RootState) =>
140
    useProfileExperience
141
      ? getExperienceSkillsByApplicant(state, { applicantId })
142
      : getExperienceSkillsByApplication(state, { applicationId });
143
  const experienceSkills = useSelector(expSkillSelector);
144
  return experienceSkills;
145
}
146
147
export function useJobPosterQuestions(
148
  jobId: number | undefined,
149
): JobPosterQuestion[] {
150
  return useSelector((state: RootState) =>
151
    jobId ? getJobPosterQuestionsByJob(state, { jobId }) : [],
152
  );
153
}
154
155
export function useJobApplicationAnswers(
156
  applicationId: number,
157
): JobApplicationAnswer[] {
158
  return useSelector((state: RootState) =>
159
    getJobApplicationAnswers(state, { applicationId }),
160
  );
161
}
162
163
/**
164
 * Return all skills from the redux store, and fetch the skills from backend if they are not yet in the store.
165
 * @param dispatch
166
 */
167
export function useFetchSkills(dispatch: DispatchType): Skill[] {
168
  const skills = useSelector(getSkills);
169
  const skillsUpdating = useSelector(getSkillsUpdating);
170
  useEffect(() => {
171
    if (skills.length === 0 && !skillsUpdating) {
172
      dispatch(fetchSkills());
173
    }
174
  }, [skills.length, skillsUpdating, dispatch]);
175
  return skills;
176
}
177
178
/**
179
 * Return all Experience constants from the redux store, and fetch them from backend if they are not yet in the store.
180
 * @param dispatch
181
 */
182
export function useFetchExperienceConstants(
183
  dispatch: DispatchType,
184
): {
185
  awardRecipientTypes: AwardRecipientType[];
186
  awardRecognitionTypes: AwardRecognitionType[];
187
  educationTypes: EducationType[];
188
  educationStatuses: EducationStatus[];
189
} {
190
  const awardRecipientTypes = useSelector(getAwardRecipientTypes);
191
  const awardRecipientTypesLoading = useSelector(
192
    (state: RootState) => state.awardRecipientType.loading,
193
  );
194
  useEffect(() => {
195
    if (awardRecipientTypes.length === 0 && !awardRecipientTypesLoading) {
196
      dispatch(fetchAwardRecipientTypes());
197
    }
198
  }, [awardRecipientTypes, awardRecipientTypesLoading, dispatch]);
199
200
  const awardRecognitionTypes = useSelector(getAwardRecognitionTypes);
201
  const awardRecognitionTypesLoading = useSelector(
202
    (state: RootState) => state.awardRecognitionType.loading,
203
  );
204
  useEffect(() => {
205
    if (awardRecognitionTypes.length === 0 && !awardRecognitionTypesLoading) {
206
      dispatch(fetchAwardRecognitionTypes());
207
    }
208
  }, [awardRecognitionTypes, awardRecognitionTypesLoading, dispatch]);
209
210
  const educationTypes = useSelector(getEducationTypes);
211
  const educationTypesLoading = useSelector(
212
    (state: RootState) => state.educationType.loading,
213
  );
214
  useEffect(() => {
215
    if (educationTypes.length === 0 && !educationTypesLoading) {
216
      dispatch(fetchEducationTypes());
217
    }
218
  }, [educationTypes, educationTypesLoading, dispatch]);
219
220
  const educationStatuses = useSelector(getEducationStatuses);
221
  const educationStatusesLoading = useSelector(
222
    (state: RootState) => state.educationStatus.loading,
223
  );
224
  useEffect(() => {
225
    if (educationStatuses.length === 0 && !educationStatusesLoading) {
226
      dispatch(fetchEducationStatuses());
227
    }
228
  }, [educationStatuses, educationStatusesLoading, dispatch]);
229
230
  return {
231
    awardRecipientTypes,
232
    awardRecognitionTypes,
233
    educationTypes,
234
    educationStatuses,
235
  };
236
}
237
238
/**
239
 * Return an Application (normalized, ie without Review) from the redux store, and fetch it from backend if it is not yet in the store.
240
 * @param applicationId
241
 * @param dispatch
242
 */
243
export function useFetchApplication(
244
  applicationId: number,
245
  dispatch: DispatchType,
246
): ApplicationNormalized | null {
247
  const applicationSelector = (
248
    state: RootState,
249
  ): ApplicationNormalized | null =>
250
    getApplicationNormalized(state, { applicationId });
251
  const application: ApplicationNormalized | null = useSelector(
252
    applicationSelector,
253
  );
254
  const applicationIsUpdating = useSelector((state: RootState) =>
255
    getApplicationIsUpdating(state, { applicationId }),
256
  );
257
  useEffect(() => {
258
    if (application === null && !applicationIsUpdating) {
259
      dispatch(fetchApplicationNormalized(applicationId));
260
    }
261
  }, [application, applicationId, applicationIsUpdating, dispatch]);
262
  return application;
263
}
264
265
/**
266
 * Return an Job from the redux store, and fetch it from backend if it is not yet in the store.
267
 * @param jobId
268
 * @param dispatch
269
 */
270
export function useFetchJob(
271
  jobId: number | undefined,
272
  dispatch: DispatchType,
273
): Job | null {
274
  const job = useJob(jobId);
275
  const jobUpdatingSelector = (state: RootState) =>
276
    jobId ? getJobIsUpdating(state, jobId) : false;
277
  const jobIsUpdating = useSelector(jobUpdatingSelector);
278
  useEffect(() => {
279
    // If job is null and not already updating, fetch it.
280
    if (jobId && job === null && !jobIsUpdating) {
281
      dispatch(fetchJob(jobId));
282
    }
283
  }, [jobId, job, jobIsUpdating, dispatch]);
284
  return job;
285
}
286
287
/**
288
 * Return all Experience relavant to an Application from the redux store, and fetch it from backend if it is not yet in the store.
289
 * @param applicationId
290
 * @param application
291
 * @param dispatch
292
 */
293
export function useFetchExperience(
294
  applicationId: number,
295
  application: ApplicationNormalized | null,
296
  dispatch: DispatchType,
297
): {
298
  experiences: ExperienceType[];
299
  experiencesUpdating: boolean;
300
  experiencesFetched: boolean;
301
} {
302
  const applicantId = application?.applicant_id ?? 0;
303
304
  // When an Application is still a draft, use Experiences associated with the applicant profile.
305
  // When an Application has been submitted and is no longer a draft, display Experience associated with the Application directly.
306
  const applicationLoaded = application !== null;
307
  const useProfileExperience =
308
    application === null ||
309
    application.application_status_id === ApplicationStatusId.draft;
310
311
  const experiences = useExperiences(applicationId, application);
312
  const experiencesUpdating = useSelector((state: RootState) =>
313
    useProfileExperience
314
      ? getUpdatingByApplicant(state, { applicantId })
315
      : getUpdatingByApplication(state, { applicationId }),
316
  );
317
  const [experiencesFetched, setExperiencesFetched] = useState(false);
318
  useEffect(() => {
319
    // Only load experiences if they have never been fetched by this component (!experiencesFetched),
320
    //  have never been fetched by another component (length === 0),
321
    //  and are not currently being fetched (!experiencesUpdating).
322
    // Also, wait until application has been loaded so the correct source can be determined.
323
    if (
324
      applicationLoaded &&
325
      !experiencesFetched &&
326
      !experiencesUpdating &&
327
      experiences.length === 0
328
    ) {
329
      setExperiencesFetched(true);
330
      if (useProfileExperience) {
331
        dispatch(fetchExperienceByApplicant(applicantId));
332
      } else {
333
        dispatch(fetchExperienceByApplication(applicationId));
334
      }
335
    }
336
  }, [
337
    applicantId,
338
    applicationId,
339
    applicationLoaded,
340
    dispatch,
341
    experiences.length,
342
    experiencesFetched,
343
    experiencesUpdating,
344
    useProfileExperience,
345
  ]);
346
  return {
347
    experiences,
348
    experiencesUpdating,
349
    experiencesFetched,
350
  };
351
}
352
353
/**
354
 * Trigger fetches for all data needed for the Application process which is not yet in the redux store, or in the process of loading.
355
 * @param applicationId
356
 */
357
export function useFetchAllApplicationData(
358
  applicationId: number,
359
  dispatch: DispatchType,
360
): {
361
  applicationLoaded: boolean;
362
  jobLoaded: boolean;
363
  experiencesLoaded: boolean;
364
  experienceConstantsLoaded: boolean;
365
  skillsLoaded: boolean;
366
} {
367
  const application = useFetchApplication(applicationId, dispatch);
368
  const jobId = application?.job_poster_id;
369
  const job = useFetchJob(jobId, dispatch);
370
  const { experiences, experiencesUpdating } = useFetchExperience(
371
    applicationId,
372
    application,
373
    dispatch,
374
  );
375
  const {
376
    awardRecipientTypes,
377
    awardRecognitionTypes,
378
    educationTypes,
379
    educationStatuses,
380
  } = useFetchExperienceConstants(dispatch);
381
  const skills = useFetchSkills(dispatch);
382
383
  return {
384
    applicationLoaded: application !== null,
385
    jobLoaded: job !== null,
386
    experiencesLoaded: !experiencesUpdating || experiences.length > 0,
387
    experienceConstantsLoaded:
388
      awardRecipientTypes.length > 0 &&
389
      awardRecognitionTypes.length > 0 &&
390
      educationTypes.length > 0 &&
391
      educationStatuses.length > 0,
392
    skillsLoaded: skills.length > 0,
393
  };
394
}
395